package pl.edu.agh.student.nn.loader;

import pl.edu.agh.student.nn.core.*;
import pl.edu.agh.student.nn.core.bp.BPLayer;
import pl.edu.agh.student.nn.core.bp.BpNeuron;
import pl.edu.agh.student.nn.core.cp.GrossbergLayer;
import pl.edu.agh.student.nn.core.kohonen.KohonenLayer;
import pl.edu.agh.student.nn.core.kohonen.KohonenNeuron;
import pl.edu.agh.student.nn.utils.InputParserUtils;

import java.io.BufferedReader;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

/**
 * Wczytuje siec z pliku (opis formatu pliku 'docs/format.input)
 */
public class NetworkLoader {
    public static NeuralNetwork loadFromFile(String filename) throws Exception {
        int i, j;
        Layer prevLayer;

        BufferedReader in = new BufferedReader(new FileReader(filename));
        // S|9$S|2 S|4$KOH|1|4$4#siec neuronowa
        // 9 neuronow wejsciowych, 2 warstwy ukryte, warstwa wyjsciowa Kohonena 4x1, 4 elementowy zbior testowy
        String[] input = in.readLine().split("#");
        String[] networkDetails = input[0].split("\\$");
        NeuralNetwork neuralNetwork = new NeuralNetwork();

        //warstwa wejsciowa
        neuralNetwork.addLayer(Layer.createLayer(networkDetails[0]));
        Layer inputLayer = neuralNetwork.getLayers().get(0);
        prevLayer = inputLayer;

        //warstwy ukryte
        String layers[] = networkDetails[1].split(" ");
        for (String l : layers) {
            if (l.trim().length() == 0)
                continue;
            Layer currentLayer = Layer.createLayer(l);
            neuralNetwork.addLayer(currentLayer);
            if(prevLayer.getType() == LayerType.BP)
                ((BPLayer) prevLayer).setNextLayer((BPLayer) currentLayer);
            prevLayer = currentLayer;
        }

        //warstwa wyjsciowa
        Layer currentLayer = Layer.createLayer(networkDetails[2]);
        neuralNetwork.addLayer(currentLayer);
        if(prevLayer.getType() == LayerType.BP)
                ((BPLayer) prevLayer).setNextLayer((BPLayer) currentLayer);

        //dane testowe
        int trainingDataSize;
        boolean loadExpectedResults = false;
        if (networkDetails[3].contains("|")) {
            String trainingSetDetails[] = networkDetails[3].split("\\|");
            trainingDataSize = Integer.parseInt(trainingSetDetails[0]);
            loadExpectedResults = trainingSetDetails[1].compareTo("true") == 0;
        } else {
            trainingDataSize = Integer.parseInt(networkDetails[3]);
        }
        TrainingSet trainingSet;
        if (trainingDataSize > 0) {
            Layer outputLayer = neuralNetwork.getOutputLayer();
            for (i = 0; i < trainingDataSize; i++) {
                trainingSet = new TrainingSet();
                trainingSet.setElements(new double[inputLayer.getSize()]);
                trainingSet.setExpected(new double[outputLayer.getSize()]);
                neuralNetwork.getLerningData().add(trainingSet);
            }
            for (i = 0; i < inputLayer.getSize(); i++) {
                String[] trainingData = in.readLine().split("\\s+");
                for (j = 0; j < trainingDataSize; j++) {
                    trainingSet = neuralNetwork.getLerningData().get(j);
                    trainingSet.getElements()[i] = InputParserUtils.getDouble(trainingData[j]);
                }
            }
            if (loadExpectedResults) {
                in.readLine(); // --- separator
                for (i = 0; i < outputLayer.getSize(); i++) {
                    String[] expectedData = in.readLine().split("\\s+");
                    for (j = 0; j < trainingDataSize; j++) {
                        trainingSet = neuralNetwork.getLerningData().get(j);
                        trainingSet.getExpected()[i] = InputParserUtils.getDouble(expectedData[j]);
                    }
                }
            }
        }

        //wczytywanie warstw
        createLayers(in, neuralNetwork);

        in.close();
        return neuralNetwork;
    }

    private static void createLayers(BufferedReader in, NeuralNetwork neuralNetwork) throws Exception {
        for (int i = 0; i < neuralNetwork.getLayers().size(); i++) {
            Layer layer = neuralNetwork.getLayers().get(i);
            String[] layerLine = in.readLine().split("#");
            layer.setName(layerLine[1]);
            String[] layerDetails = layerLine[0].split("\\$");

            if (layer.getType() == LayerType.DELTA)
                ((GrossbergLayer) layer).setActivationFunction(ActivationFunction.getFunction(layerDetails[0]));

            int paramsSize = layerDetails[3].trim().length() == 0 ? 0 : Integer.parseInt(layerDetails[3]);
            List<Integer> intParams = null;
            List<Double> doubleParams = null;

            for (int k = 0; k < paramsSize; k++) {
                String[] params = in.readLine().split("\\s+");
                boolean intValues = params[0].trim().toUpperCase().compareTo("I") == 0;
                if (intValues)
                    intParams = new ArrayList<Integer>();
                else
                    doubleParams = new ArrayList<Double>();
                for (int m = 2; m < params.length; m++) {
                    if (intValues) {
                        intParams.add(Integer.parseInt(params[m]));
                    } else {
                        doubleParams.add(Double.parseDouble(params[m]));
                    }
                }
                if (intValues) {
                    layer.getIntParameters().put(LayerParameter.valueOf(params[1]), intParams);
                } else {
                    layer.getDoubleParameters().put(LayerParameter.valueOf(params[1]), doubleParams);
                }
            }
            for (int j = 0; j < layer.getSize(); j++) {
                Neuron neuron = createNeuron(in, layerDetails[1], layerDetails[2], j, layer,
                        i == 0 ? null : neuralNetwork.getLayers().get(i - 1));
                neuron.setActivationFunction(ActivationFunction.getFunction(layerDetails[0]));
                layer.addNeuron(neuron);
            }
        }
    }

    private static Neuron createNeuron(BufferedReader in, String bias, String generator, int index, Layer layer, Layer prevLayer) throws Exception {
        Neuron neuron = null;
        if (prevLayer == null) {
            neuron = new InputNeuron();
        } else {
            switch (layer.getType()) {
                case DELTA:
                case WH:
                case S:
                    neuron = new InputOutputNeuron();
                    break;
                case BP:
                    neuron = new BpNeuron();
                    break;
                case KOH:
                    neuron = new KohonenNeuron();
                    Vector<Integer> v = new Vector<Integer>();
                    v.add(index % ((KohonenLayer) layer).getWidth());
                    v.add(index / ((KohonenLayer) layer).getWidth());
                    ((KohonenNeuron) neuron).setV(v);
                    ((KohonenNeuron) neuron).setF(
                            1.0 / ((double)
                                    (((KohonenLayer) layer).getWidth()
                                            * ((KohonenLayer) layer).getHeight())));
                    break;
            }
        }
        String[] neuronDetails = null;
        if (bias.length() != 0) {
            neuron.setBias(InputParserUtils.getDouble(bias));
            neuron.isBias(true);
        } else {
            neuron.isBias(false);
        }

        if (generator.length() == 0) {
            neuronDetails = in.readLine().split(" ");
        }

        if (prevLayer == null) {
            if (generator.length() != 0) {
                ((InputNeuron) neuron).setInput(InputParserUtils.getDouble(generator));
            } else {
                ((InputNeuron) neuron).setInput(InputParserUtils.getDouble(neuronDetails[0]));
            }
        } else {
            if(neuron.hasBias() && generator.length() == 0) {
                neuron.setBias(InputParserUtils.getDouble(neuronDetails[0]));
            }
            for (int i = 0; i < prevLayer.getSize(); i++) {
                Connection connection = new Connection();
                if (generator.length() != 0) {
                    connection.setWeight(InputParserUtils.getDouble(generator));
                } else {
                    connection.setWeight(InputParserUtils.getDouble(neuronDetails[i + 1]));
                }
                connection.setFromNeuron(prevLayer.getNeurons().get(i));
                connection.setToNeuron(neuron);
                ((InputOutputNeuron) neuron).addConnection(connection);
                prevLayer.getNeurons().get(i).getOutputConnections().add(connection);
            }
        }
        return neuron;
    }
}
